package co.edu.udea.iw.prestamos.bl;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.stereotype.Service;

import co.edu.udea.iw.prestamos.modelo.dao.DispositivoDAO;
import co.edu.udea.iw.prestamos.modelo.dto.Dispositivo;
import co.edu.udea.iw.prestamos.modelo.dto.DispositivoList;
import co.edu.udea.iw.prestamos.modelo.dto.Respuesta;
import co.edu.udea.iw.prestamos.util.ConstantesEstaticas;
import co.edu.udea.iw.prestamos.util.exception.DatabaseException;
import co.edu.udea.iw.prestamos.util.exception.RegistryNotFoundException;
import co.edu.udea.iw.prestamos.util.logger.Log;
import co.edu.udea.iw.prestamos.util.ppt.LeerPropiedades;

/**
 * Lógica de negocio para la gestión de dispositivos.
 * 
 * @author Dave Rivera <daverivera90@gmail.com>
 * @since 16-07-2014
 * @version 1.0
 */
@Configurable
@Service("dispositivoBL")
public class DispositivoBL {
	
	/**
	 * Inyectamos las dependencias de la clase.
	 */
	@Autowired
	private DispositivoDAO dispositivoDao;
	
	@Autowired
	private PrestamoBL prestamoBL;
	
	@Autowired
    private Validator validator;
	
	@Autowired
	private LeerPropiedades leerPropiedades;
	
	/**
	 * Devuelve una lista con todos los dispositivos que se encuentran
	 * disponibles para préstamo.
	 * 
	 * @return lista de dispositivos disponibles.
	 */
	public Object obtenerDispositivosDisponibles() {
		List<Dispositivo> dispositivos = dispositivoDao.obtenerDisponibles();
		
		return convertToDispositivoList(dispositivos);
	}
	
	/**
	 * Devuelve una lista con todos los dispositivos que han sido dados de
	 * baja del sistema.
	 * 
	 * @return lista de dispositivos no disponibles.
	 */
	public Object obtenerDispositivosNoDisponibles() {
		List<Dispositivo> dispositivos =  dispositivoDao.obtenerNoDisponibles();
		
		return convertToDispositivoList(dispositivos);
	}
	
	/**
	 * Devuelve una lista con todos los dispositivos que se encuentran
	 * disponibles para préstamo en una fecha determinada.
	 * 
	 * @param fecha Fecha en la que se requiere la disponibilidad de los
	 *               dispositivos.
	 * @return lista de dispositivos disponibles para la fecha dada.
	 */
	public Object obtenerDisponiblesFecha(String fecha){
		Date date;
		try {
			date = new SimpleDateFormat("dd-mm-yyyy").parse(fecha);
		} catch (ParseException e) {
			Log.log(getClass(),
					leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_EXCEPCIONES, ConstantesEstaticas.DISPOSITIVO_EXCEPCION_FECHA_PARSE), 
					Log.ERROR, 
					e
				);
			Log.log(getClass(), "Fecha :" + fecha, Log.INFO, null);
			
			Respuesta respuesta = new Respuesta();
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_OBTENER_ERROR_FECHA));
			return respuesta;
		}
		
		List<Dispositivo> dispositivos = dispositivoDao.obtenerDisponiblesFecha(date);
		
		return convertToDispositivoList(dispositivos);
	}
	
	/**
	 * Devuelve un dispositivo identificado con el código pasado como
	 * parámetro. Si el dispositivo no existe, se retorna una respuesta de
	 * error.
	 * 
 	 * @param id Identificación del dispositivo.
	 * @return dispositivo consultado, si existe. En caso contrario, retorna
	 *          una respuesta de error con el resultado de la operación. 
	 */
	public Object obtenerDispositivo(int id) {
		try {
			Dispositivo dispositivo = dispositivoDao.obtenerPorId(id);
			return dispositivo;
		} catch (RegistryNotFoundException e) {
			Log.log(getClass(), 
					leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.DISPOSITIVO_INVALID_ID), 
					Log.ERROR, 
					e
				);
		}
		
		Respuesta respuesta = new Respuesta();
		respuesta.setTipo(Respuesta.ERROR);
		respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_INVALID_ID));
		return respuesta;		
	}
	
	/**
	 * Cambia la disponibilidad del dispositivo identificado con el código
	 * pasado como parámetro. Si la disponibilidad es true, se reactiva el
	 * dispositivo; en caso contrario, se da de baja al dispositivo. Si el
	 * dispositivo no existe, se retorna una respuesta de error y se cancela la
	 * operación.
	 * 
	 * @param id Identifiación del dispositivo a modificar.
	 * @param disponibilidad Estado nuevo de disponibilidad del dispositivo.
	 * @return respuesta con el resultado de la operación.
	 */
	public Object cambiarDisponibilidad(int id, boolean disponibilidad) {
		Respuesta respuesta = new Respuesta();
		
		try {
			Dispositivo dispositivo = dispositivoDao.obtenerPorId(id);
			dispositivo.setDisponible(disponibilidad);
			dispositivoDao.actualizar(dispositivo);
			
			// Si no es disponible dado de baja -> Cancelar solicitudes
			if (!disponibilidad) 
				cancelarSolicitudes(id);

			respuesta.setTipo(Respuesta.SUCCESS);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_DISPONIBILIDAD_SUCCESS));
		} catch (RegistryNotFoundException e) {
			Log.log(getClass(), 
					leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.DISPOSITIVO_INVALID_ID), 
					Log.ERROR, 
					e
				);
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_INVALID_ID));
		} catch (DatabaseException e) {
			Log.log(getClass(), 
					leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.DISPOSITIVO_DISPONIBILIDAD_ERROR_BD), 
					Log.ERROR, 
					e
				);
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_DISPONIBILIDAD_ERROR_BD));
		}

		return respuesta;		
	}
	
	
	/**
	 * Registra un dispositivo en el sistema. Si la información enviada como
	 * parámetro es nula o está incompleta, se retorna una respuesta de error y
	 * se cancela la operación.
	 * 
	 * @param dispositivo Información del nuevo dispositivo.
	 * @return respuesta con el resultado de la operación.
	 */
	public Object registrarDispositivo(Dispositivo dispositivo) {
		Respuesta respuesta = new Respuesta();
		
		if(dispositivo == null){
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_REGISTRO_ERROR_NULL));
			return respuesta;
		}
		
		Set< ConstraintViolation<Dispositivo> > constraintViolations = validator.validate( dispositivo );
		if (!constraintViolations.isEmpty()){
			Log.log(getClass(), leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.DISPOSITIVO_REGISTRO_ERROR_MISSINGDATA) + ": " + constraintViolations.size(), Log.INFO, null);
			Log.log(getClass(), constraintViolations.toString(), Log.INFO, null);
			
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(constraintViolations.toString());
			
			return respuesta;
		}
		
		try {
			dispositivoDao.crear(dispositivo);
			
			respuesta.setTipo(Respuesta.SUCCESS);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, 
					             ConstantesEstaticas.DISPOSITIVO_REGISTRO_SUCCESS));
			return respuesta;
						
		} catch (DatabaseException e) {
			Log.log(getClass(), leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.DISPOSITIVO_REGISTRO_ERROR_BD), Log.ERROR,	e);
		}
		
		respuesta.setTipo(Respuesta.ERROR);
		respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.DISPOSITIVO_REGISTRO_ERROR_BD));
		return respuesta;
		
	}
	
	/**
	 * Cancela las solicitudes asociadas al dispositivo identificado con el
	 * código pasado como parámetro.
	 * 
	 * @param id Identificación del dispositivo.
	 */
	private void cancelarSolicitudes(int id){
		prestamoBL.cancelarSolicitudes(id);
	}
	
	
	/**
	 * Convierte una lista de tipo List con ítems de tipo Dispositivo, en un
	 * objeto de tipo DispositivoList.
	 * 
	 * @param dispositivos Lista de dispositivos, de tipo List.
	 * @return objeto del tipo DispositivoList con los ítems de la lista
	 *          original.
	 */
	private DispositivoList convertToDispositivoList(List<Dispositivo> dispositivos){
		DispositivoList dispositivoList = new DispositivoList();
		
		for (Dispositivo dispositivo : dispositivos)
			dispositivoList.add(dispositivo);
		
		return dispositivoList;
	}
}
